use crate::{
    db::models::{Contract, Invoice},
    schema::{
        contract::{self},
        invoice::{self, dsl::*},
        project,
        provider::{self},
    },
};
use actix_web::{
    post,
    web::{self, Data, Json},
    HttpResponse,
};
use diesel::{dsl::sql, insert_into, sql_types::Integer, update};
use diesel::{prelude::*, sql_types::Bool};

use crate::{api::Page, DbPool};

#[derive(Debug, serde::Deserialize)]
pub struct PageInvoiceReq {
    page: i64,
    size: i64,
    project_id: i32,
    provider_id: i32,
    contract_id: i32,
}

#[derive(Debug, serde::Serialize, Queryable)]
pub struct InvoiceVo {
    id: i32,
    key: i32,
    project_id: i32,
    project_name: String,
    provider_id: i32,
    provider_name: String,
    contract_id: i32,
    contract_no: String,
    amount: f64,
    invoice_date: i64,
    remarks: Option<String>,
}

const SIZE_MAX: i64 = 50000000000;
const SIZE_INVERSE: i64 = -1;

#[post("/page")]
async fn page_invoice(
    db: Data<DbPool>,
    request: web::Json<PageInvoiceReq>,
) -> Json<Page<InvoiceVo>> {
    let req = request.0;
    let data = web::block(move || {
        let mut conn = db.get().unwrap();
        let mut size = req.size;
        if req.size.eq(&SIZE_INVERSE) {
            size = SIZE_MAX;
        }
        let data = invoice::table
            .inner_join(
                contract::table
                    .inner_join(provider::table)
                    .inner_join(project::table),
            )
            .filter(
                sql::<Bool>("(invoice.contract_id = ")
                    .bind::<Integer, _>(req.contract_id)
                    .sql(" or -1 = ")
                    .bind::<Integer, _>(req.contract_id)
                    .sql(" ) and ( project_id = ")
                    .bind::<Integer, _>(req.project_id)
                    .sql(" or -1 = ")
                    .bind::<Integer, _>(req.project_id)
                    .sql(") and ( provider_id = ")
                    .bind::<Integer, _>(req.provider_id)
                    .sql(" or -1 = ")
                    .bind::<Integer, _>(req.provider_id)
                    .sql(") "),
            )
            .limit(size)
            .offset((req.page - 1) * size)
            .select((
                invoice::id,
                invoice::id,
                project::id,
                project::name,
                provider::id,
                provider::name,
                contract::id,
                contract::no,
                invoice::amount,
                invoice::invoice_date,
                invoice::remarks,
            ))
            .load::<InvoiceVo>(&mut conn);
        let c: Result<i64, _> = invoice::table
            .inner_join(
                contract::table
                    .inner_join(provider::table)
                    .inner_join(project::table),
            )
            .filter(
                sql::<Bool>("(invoice.contract_id = ")
                    .bind::<Integer, _>(req.contract_id)
                    .sql(" or -1 = ")
                    .bind::<Integer, _>(req.contract_id)
                    .sql(" ) and ( project_id = ")
                    .bind::<Integer, _>(req.project_id)
                    .sql(" or -1 = ")
                    .bind::<Integer, _>(req.project_id)
                    .sql(") and ( provider_id = ")
                    .bind::<Integer, _>(req.provider_id)
                    .sql(" or -1 = ")
                    .bind::<Integer, _>(req.provider_id)
                    .sql(") "),
            )
            .select(diesel::dsl::count(invoice::id))
            .first(&mut conn);
        match data {
            Ok(d) => Page {
                total: c.unwrap(),
                content: d,
            },
            Err(e) => {
                println!("{:?}", e);
                return Page {
                    total: 0,
                    content: vec![],
                };
            }
        }
    })
    .await;
    Json(data.unwrap())
}

#[derive(Debug, serde::Deserialize)]
pub struct CreateInvoiceVo {
    contract_no: i32,
    invoice_date: i64,
    amount: f64,
    remarks: Option<String>,
}

#[post("/create")]
async fn create_invoice(db: Data<DbPool>, request: web::Json<CreateInvoiceVo>) -> HttpResponse {
    let req = request.0;
    let data = web::block(move || {
        let mut conn = db.get().unwrap();
        // 判断合同是否存在
        let the_contract: Result<Contract, _> = contract::dsl::contract
            .filter(contract::id.eq(&req.contract_no))
            .first::<Contract>(&mut conn);
        if let Err(_) = the_contract {
            return 1;
        }
        let the_contract = the_contract.unwrap();

        let invoice_to_save = Invoice {
            id: 0,
            contract_id: the_contract.id,
            amount: req.amount,
            invoice_date: req.invoice_date,
            remarks: req.remarks,
        };
        let result = insert_into(invoice)
            .values(&invoice_to_save)
            .execute(&mut conn);

        match result {
            Ok(_) => 200,
            _ => 0,
        }
    })
    .await;
    match data.unwrap() {
        0 => HttpResponse::BadRequest().body("合同已存在"),
        1 => HttpResponse::BadRequest().body("项目不存在"),
        2 => HttpResponse::BadRequest().body("供应商不存在"),
        3 => HttpResponse::BadRequest().body("主合同不存在"),
        _ => HttpResponse::Ok().json(true),
    }
}

#[derive(Debug, serde::Deserialize)]
pub struct UpdateInvoiceVo {
    id: i32,
    contract_no: i32,
    invoice_date: i64,
    amount: f64,
    remarks: Option<String>,
}

#[post("/update")]
async fn update_invoice(db: Data<DbPool>, request: web::Json<UpdateInvoiceVo>) -> HttpResponse {
    let req = request.0;
    let data = web::block(move || {
        let mut conn = db.get().unwrap();

        // 判断合同是否存在
        let the_contract: Result<Contract, _> = contract::dsl::contract
            .filter(contract::id.eq(&req.contract_no))
            .first::<Contract>(&mut conn);
        if let Err(_) = the_contract {
            return 1;
        }

        let the_contract = the_contract.unwrap();

        let result: Result<usize, _> = update(invoice.filter(invoice::id.eq(req.id)))
            .set((
                invoice::contract_id.eq(the_contract.id),
                invoice::amount.eq(req.amount),
                invoice::invoice_date.eq(req.invoice_date),
                invoice::remarks.eq(req.remarks),
            ))
            .execute(&mut conn);
        match result {
            Ok(_) => 200,
            _ => 0,
        }
    })
    .await;
    match data.unwrap() {
        0 => HttpResponse::BadRequest().body("合同已存在"),
        1 => HttpResponse::BadRequest().body("项目不存在"),
        2 => HttpResponse::BadRequest().body("供应商不存在"),
        3 => HttpResponse::BadRequest().body("主合同不存在"),
        _ => HttpResponse::Ok().json(true),
    }
}

#[derive(Debug, serde::Deserialize)]
pub struct DeleteInvoiceReq {
    id: i32,
}

#[post("/delete")]
async fn delete_invoice(db: Data<DbPool>, request: web::Json<DeleteInvoiceReq>) -> HttpResponse {
    let req = request.0;
    let data = web::block(move || {
        let mut conn = db.get().unwrap();
        let result = diesel::delete(invoice.filter(invoice::id.eq(req.id))).execute(&mut conn);
        match result {
            Ok(d) => d,
            _ => 0,
        }
    })
    .await;
    match data.unwrap() {
        0 => HttpResponse::BadRequest().body("未找到记录"),
        _ => HttpResponse::Ok().json(true),
    }
}
